package com.zbycorp.generatecode.service.impl;

import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.zbycorp.generatecode.entity.TableInfo;
import com.zbycorp.generatecode.entity.Template;
import com.zbycorp.generatecode.service.CodeGenerateService;
import com.zbycorp.generatecode.service.SettingsStorageService;
import com.zbycorp.generatecode.utils.*;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author YCL
 * @date 2023/3/29
 */
@State(name = "GenerateCodeSetting", storages = @Storage("generateCodeSetting.xml"))
public class CodeGenerateServiceImpl implements CodeGenerateService {

    private FileUtils fileUtils = FileUtils.getInstance();
    private Project project;
    private TableInfo tableInfo;

    private final List<String> ENTITY_LIST = Arrays.asList("Dto", "entity", "DO");
    /**
     * 导入包时过滤的包前缀
     */
    private static final String FILTER_PACKAGE_NAME = "java.lang";
    /**
     * Mapper.xml模板内容
     */
    private static final String MAPPER_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n\n<mapper namespace=\"$!{savePackage}.$!{tableName}Mapper\">\n    <!--  以下仅用于个性化查询的时候的示例，默认情况下，基本的增删改查都不需要单独定义，在代码层面使用Wrapper子类即可完成 -->\n    <!-- 其它SQL语句 -->\n    <!--\n    <select id=\"getByUserId\" resultType=\"$!{tool.firstLowerCase($tableName)}DO\"\n            parameterType=\"java.lang.String\">\n        <![CDATA[SELECT * from xxxx WHERE user_id = #{userId}]]>\n    </select>\n    -->\n</mapper>";

    @Override
    public void generate(Project project, TableInfo tableInfo, List<Template> templateList) {
        if (project == null || CollectionUtil.isEmpty(templateList) || tableInfo == null) {
            return;
        }
        this.project = project;
        this.tableInfo = tableInfo;

        HashMap<String, TreeSet<String>> autoImportMap = getAutoImportMap(templateList);

        for (Template template : templateList) {
            String content = template.getCode();
            if (StringUtil.isEmpty(content)) {
                continue;
            }
            content = addGlobalConfig(content);
            // 构建参数
            Map<String, Object> param = getDefaultParam();
            param.put("savePackage", template.getSavePackage());

            TreeSet<String> importList = autoImportMap.getOrDefault(template.getDefaultName(), new TreeSet<>());
            if (ENTITY_LIST.contains(template.getDefaultName())) {
                param.put("tableInfo", tableInfo);
                importList.addAll(getImportList());
            }
            param.put("importList", importList);

            if ("DO".equals(template.getDefaultName())) {
                param.put("table_name", tableInfo.getObj().getName());
            }
            if ("Facade".equals(template.getDefaultName())) {
                param.put("projectName", project.getName());
            }

            // 开始生成
            content = VelocityUtils.generate(content, param);

            String savePath = template.getSavePath() + "/" + template.getSavePackage();
            savePath = savePath.replace(".", "/");

            write(savePath, template.getClassName() + ".java", content);
            //如果是Mapper类，同时生成mapper.xml文件
            if ("Mapper".equals(template.getDefaultName())) {
                String xmlPath = savePath.substring(0, savePath.indexOf("src/main/java")) + "src/main/resources/mybatis/mapper";
                String xmlContent = VelocityUtils.generate(MAPPER_XML, param);
                write(xmlPath, template.getClassName() + ".xml", xmlContent);
            }
        }
    }

    /**
     * 获取导入列表
     *
     * @return 导入列表
     */
    private Set<String> getImportList() {
        // 创建一个自带排序的集合
        Set<String> result = new TreeSet<>();
        tableInfo.getFullColumn().forEach(columnInfo -> {
            if (!columnInfo.getType().startsWith(FILTER_PACKAGE_NAME)) {
                String type = NameUtils.getInstance().getClsFullNameRemoveGeneric(columnInfo.getType());
                result.add(type);
            }
        });
        return result;
    }

    private HashMap<String, TreeSet<String>> getAutoImportMap(List<Template> templateList) {
        HashMap<String, TreeSet<String>> autoImportMap = new HashMap<>();
        Map<String, String> templateMap = templateList.stream().collect(Collectors.toMap(Template::getDefaultName, i -> i.getSavePackage() + "." + i.getClassName()));
        String dto = templateMap.getOrDefault("Dto", null);
        String addCmd = templateMap.getOrDefault("AddCmd", null);
        String addCmdExe = templateMap.getOrDefault("AddCmdExe", null);
        String deleteCmd = templateMap.getOrDefault("DeleteCmd", null);
        String deleteCmdExe = templateMap.getOrDefault("DeleteCmdExe", null);
        String updateCmd = templateMap.getOrDefault("UpdateCmd", null);
        String updateCmdExe = templateMap.getOrDefault("UpdateCmdExe", null);
        String getQry = templateMap.getOrDefault("GetQry", null);
        String getQryExe = templateMap.getOrDefault("GetQryExe", null);
        String listQry = templateMap.getOrDefault("ListQry", null);
        String listQryExe = templateMap.getOrDefault("ListQryExe", null);
        String entity = templateMap.getOrDefault("entity", null);
        String repository = templateMap.getOrDefault("Repository", null);
        String cmdConverter = templateMap.getOrDefault("CmdConverter", null);
        String aDo = templateMap.getOrDefault("DO", null);
        String application = templateMap.getOrDefault("Application", null);
        String mapper = templateMap.getOrDefault("Mapper", null);
        String converter = templateMap.getOrDefault("Converter", null);
        autoImportMap.put("AddCmd", Stream.of(dto).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("UpdateCmd", Stream.of(dto).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("ListQry", Stream.of(dto).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("Facade", Stream.of(dto, addCmd, deleteCmd, updateCmd, getQry, listQry).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("CmdConverter", Stream.of(dto, entity).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("GetQryExe", Stream.of(getQry, dto, entity, repository, cmdConverter).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("ListQryExe", Stream.of(listQry, dto, entity, repository, cmdConverter).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("AddCmdExe", Stream.of(addCmd, dto, entity, repository, cmdConverter).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("UpdateCmdExe", Stream.of(addCmd, dto, entity, repository, cmdConverter).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("DeleteCmdExe", Stream.of(deleteCmd, dto, entity, repository, cmdConverter).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("Application", Stream.of(deleteCmd, deleteCmdExe, addCmd, addCmdExe, updateCmd, updateCmdExe, dto, listQryExe, listQry, getQryExe, getQry).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("Converter", Stream.of(entity, aDo).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("Mapper", Stream.of(aDo).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("Controller", Stream.of(addCmd, deleteCmd, updateCmd, getQry, listQry, dto, application).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("FacadeImpl", Stream.of(addCmd, deleteCmd, updateCmd, getQry, listQry, dto, application).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));
        autoImportMap.put("RepositoryImpl", Stream.of(entity, aDo, mapper, repository, converter).filter(StringUtil::isNotEmpty).collect(Collectors.toCollection(TreeSet::new)));

        return autoImportMap;
    }

    /**
     * 获取默认参数
     *
     * @return 参数
     */
    private Map<String, Object> getDefaultParam() {
        Map<String, Object> param = new HashMap<>(20);
        String author = SettingsStorageService.getSettingStorage(project).getAuthor();
        // 作者
        param.put("author", StringUtil.isNotEmpty(author) ? author : System.getProperty("user.name"));
        // 日期
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        param.put("date", dateFormat.format(new Date()));
        //表名-首字母大写
        param.put("tableName", tableInfo.getName());
        //工具类
        param.put("tool", GlobalTool.getInstance());
        return param;
    }

    public static String addGlobalConfig(String template) {
        HashMap<String, String> globalConfigs = new HashMap<>();
        globalConfigs.put("autoImport.vm", "##自动导入包（仅导入实体属性需要的包，通常用于实体类）\n#foreach($import in $importList)\nimport $!import;\n#end");
        for (String name : globalConfigs.keySet()) {
            // 正则被替换字符转义处理
            String value = globalConfigs.get(name).replace("\\", "\\\\").replace("$", "\\$");

            // 将不带{}的变量加上{}
            template = template.replaceAll("\\$!?" + name + "(\\W)", "\\$!{" + name + "}$1");
            // 统一替换
            template = template.replaceAll("\\$!?\\{" + name + "}", value);

        }
        return template;
    }

    /**
     * 处理路径，统一分割符并转小写
     *
     * @param path      路径
     * @param lowerCase 是否转小写
     * @return 处理后的路径
     */
    private String handlerPath(String path, boolean lowerCase) {
        // 统一分割符
        path = path.replace("\\", "/");
        // 避免重复分割符
        path = path.replace("//", "/");
        // 统一小写
        return lowerCase ? path.toLowerCase() : path;
    }

    /**
     * 提示创建目录
     *
     * @param saveDir 保存路径
     * @return 是否放弃执行
     */
    private VirtualFile titleCreateDir(VirtualFile saveDir, VirtualFile baseDir, String savePath) {
        if (saveDir != null) {
            return saveDir;
        }
        // 尝试创建目录
        String msg = String.format("Directory %s Not Found, Confirm Create?", savePath);
        if (MessageDialogUtils.yesNo(project, msg)) {
            saveDir = fileUtils.createChildDirectory(project, baseDir, savePath);
            return saveDir;
        }

        return null;
    }

    private boolean isProjectFile(String savePath) {
        VirtualFile baseDir = ProjectUtils.getBaseDir(project);
        // 无法获取到项目基本目录，可能是Default项目，直接返回非项目文件
        if (baseDir == null) {
            return false;
        }
        // 路径对比，判断项目路径是否为文件保存路径的子路径
        String projectPath = handlerPath(baseDir.getPath(), true);
        String tmpFilePath = handlerPath(savePath, true);
        if (tmpFilePath.length() > projectPath.length()) {
            if (!"/".equals(tmpFilePath.substring(projectPath.length(), projectPath.length() + 1))) {
                return false;
            }
        }
        return tmpFilePath.indexOf(projectPath) == 0;
    }

    public void write(String savePath, String fileName, String content) {
        // 判断目录是否存在
        VirtualFile baseDir = ProjectUtils.getBaseDir(project);
        if (baseDir == null) {
            throw new IllegalStateException("项目基本路径不存在");
        }
        // 处理保存路径
        savePath = handlerPath(savePath, false);
        if (isProjectFile(savePath)) {
            // 删除保存路径的前面部分
            savePath = savePath.substring(handlerPath(baseDir.getPath(), true).length());
        } else {
            baseDir = null;
        }
        // 删除开头与结尾的/符号
        while (savePath.startsWith("/")) {
            savePath = savePath.substring(1);
        }
        while (savePath.endsWith("/")) {
            savePath = savePath.substring(0, savePath.length() - 1);
        }
        // 查找保存目录是否存在
        VirtualFile saveDir;
        if (baseDir == null) {
            saveDir = VfsUtil.findFileByIoFile(new File(savePath), false);
        } else {
            saveDir = VfsUtil.findRelativeFile(baseDir, savePath.split("/"));
        }
        // 提示创建目录
        VirtualFile directory = titleCreateDir(saveDir, baseDir, savePath);
        if (directory == null) {
            return;
        }
        VirtualFile psiFile = directory.findChild(fileName);
        // 保存或覆盖
        saveOrReplaceFile(psiFile, directory, fileName, content);
    }

    /**
     * 保存或替换文件
     *
     * @param file      文件
     * @param directory 目录
     */
    private void saveOrReplaceFile(VirtualFile file, VirtualFile directory, String fileName, String content) {
        PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
        Document document;
        // 文件不存在直接创建
        if (file == null) {
            file = fileUtils.createChildFile(project, directory, fileName);
            if (file == null) {
                return;
            }
            document = coverFile(file, fileName, content);
        } else {
            // 提示覆盖文件
            String msg = String.format("File %s Exists, Select Operate Mode?", file.getPath());
            int result = MessageDialogUtils.yesNoCancel(project, msg, "Convert", "Compare", "Cancel");
            switch (result) {
                case Messages.YES:
                    // 覆盖文件
                    document = coverFile(file, fileName, content);
                    break;
                case Messages.NO:
                case Messages.CANCEL:
                default:
                    return;
            }
        }
        // 执行代码格式化操作
        FileUtils.getInstance().reformatFile(project, file);
        // 提交文档改动，并非VCS中的提交文件
        if (document != null) {
            psiDocumentManager.commitDocument(document);
        }
    }

    /**
     * 覆盖文件
     *
     * @param file 文件
     * @param text 文件内容
     * @return 覆盖后的文档对象
     */
    private Document coverFile(VirtualFile file, String fileName, String text) {
        return FileUtils.getInstance().writeFileContent(project, file, fileName, text);
    }

}
